<?php
/**
 * Klasa obsługująca połączenie z serwerem IMAP
 * 
 * @author Marcin
 */

 class IMAP {
 	private $a = 0;
 	private $sck;
	private $username;
	private $userpass;

	private $mailboxName = 'INBOX';

	/**
	* @method __construct
	* @param $uname String Nazwa użytkownika użyta do logowania na serwerze IMAP
	* @param $upass String Hasło użytkownika użyte do logowania na serwerze IMAP
	*/
	public function __construct($uname, $upass) {
		$this->username = $uname;
		$this->userpass = $upass;
	}

	/**
	* @method connectAndLogin
	* @return Boolean TRUE, gdy połączono i zalogowano, FALSE w przeciwnym wypadku
	*/
	public function connectAndLogin() {
		$this->sck = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		if(!socket_connect($this->sck, IMAPADDR, IMAPPORT)) return FALSE;
		$buf = $this->recv();
		$this->send('LOGIN '.$this->username.' '.$this->userpass);
		$buf=$this->recv();
		
		//odpowiedź w formacie a000 (OK/NO/BAD) Wiadomość
		$buf=explode(' ', $buf);
		//sprawdzany jest tylko typ odpowiedzi, gdy OK - zalogowano
		if($buf[1] == 'OK') return TRUE;
		return FALSE;
	}

	/**
	* @method listMailboxes
	* @return array[] Tablica zawierająca informacje o dostępnych skrzynkach
	* 
	* Metoda pobierająca od serwera informacje o dostępnych dla użytkownika skrzynkach pocztowych
	*/
	public function listMailboxes() {
		$tab = array();
		$buf = '';
		
		$this->send('LIST "*" "*"');
		$buf = $this->recv();
		$tmp = explode("\n", $buf);
		for($i=0; '*'==$tmp[$i]{0}; $i++) {
			$element=explode('"', $tmp[$i]);
			//dzieląc po znaku ", czwarty element to nazwa skrzynki
			$tab[] = $element[3];
		}
		return $tab;
	}

	/**
	* @method getMailboxInfo
	* @param $mailboxName String Nazwa skrzynki pocztowej
	* @return array[] Tablica zawierająca informacje o danej skrzynce  
	*/
	public function getMailboxInfo($mailboxName) {
	$tab = array();
		$buf = '';
		
		$this->send('SELECT '.$mailboxName);
		$buf = $this->recv();
		$buf = explode("\n", $buf);
		
		//Pierwszy wiersz - ilosć istniejących wiadomości w skrzynce:  * 000 EXISTS 
		$tmp = explode(' ', $buf[0]);
		$tab['EXISTS'] = $tmp[1];
		//Kolejny: ilość wiadomości z flagą \Recent - te które zostały ostatnio przysłane
		$tmp = explode(' ', $buf[1]);
		$tab['RECENT'] = $tmp[1];
		//reszta informacji nie jest bardzo istotna z punktu widzenia aplikacji
		return $tab;
	}
	/**
	 * @method getMailHeader
	 * @param $mailNo int Numer kolejny listu
	 * @return Message Obiekt klasy Message z wypełnionymi polami nagłówka
	 * 
	 * FUNKCJA MOŻE ZOSTAĆ UŻYTA TYLKO PO WYBRANIU SKRZYNKI (@see getMailboxInfo)!
	 * Funkcja pobiera najważniejsze informacje z nagłówka wiadomości 
	 * o podanym numerze kolejnym z wybranej skrzynki (nadawca, odbiorcy, temat, data, typ zawartości)
	 */
	public function getMailHeader($mailNo) {
		$mesg = new Message();
		$tmp = $this->fetch('body[header.fields(FROM)]', $mailNo);
		$tmp = explode(':', $tmp); //ucięcie tekstu przed pierwszym ':'
		$tmp = array_slice($tmp, 1);
		$mesg->from = Helpers::Filter_HTML_Tags(
			Helpers::Base64_DecodeUTF8(
				trim(implode(':',$tmp))));
				
		$tmp = $this->fetch('body[header.fields(SUBJECT)]', $mailNo);
		$tmp = explode(':', $tmp); //ucięcie tekstu przed pierwszym ':'
		$tmp = array_slice($tmp, 1);
		$mesg->subject = Helpers::Filter_HTML_Tags(
			Helpers::Base64_DecodeUTF8(
				trim(implode(':',$tmp))));
				
		$tmp = $this->fetch('body[header.fields(DATE)]', $mailNo);
		$tmp = explode(':', $tmp); //ucięcie tekstu przed pierwszym ':'
		$tmp = array_slice($tmp, 1);
		$mesg->date = Helpers::Filter_HTML_Tags(
			Helpers::Base64_DecodeUTF8(
				trim(implode(':',$tmp))));
				
		$tmp = $this->fetch('body[header.fields(TO)]', $mailNo);
		$tmp = explode(':', $tmp); //ucięcie tekstu przed pierwszym ':'
		$tmp = array_slice($tmp, 1);
		$mesg->to = Helpers::Filter_HTML_Tags(
			Helpers::Base64_DecodeUTF8(
				trim(implode(':',$tmp))));
				
		$tmp = $this->fetch('body[header.fields(CONTENT-TYPE)]', $mailNo);
		$tmp = explode(':', $tmp); //ucięcie tekstu przed pierwszym ':'
		$tmp = array_slice($tmp, 1);
		$mesg->contenttype = Helpers::Filter_HTML_Tags(
			Helpers::Base64_DecodeUTF8(
				trim(implode(':',$tmp))));
		return $mesg;
	}
		 
	/**
	 * @method getMailContent
	 * @param $mailNo
	 * @return Message Obiekt z wypełnionymi polami nagłówka oraz treści
	 * 
	 * Metoda pobierająca całą zawartość maila (BEZ ZAŁĄCZNIKÓW!)
	 * FUNKCJA MOŻE ZOSTAĆ UŻYTA TYLKO PO WYBRANIU SKRZYNKI (@see getMailboxInfo)!
	 */
	public function getMailContent($mailNo) {
	  	$message = new Message($this->getMailHeader($mailNo));
		
		//trzeba sprawdzić, czy wiadomość nie składa się z kilku części 
		//(w Content-Type nagłówka istnieje pole boundary)
		preg_match('/boundary="(?P<bound>[0-9-]+)"/', $message->contenttype, $bound);
		$tmp = $this->fetch('BODY[content]', $mailNo);
		if(isset($bound['bound'])) {
			$parts = array();
			//podział wiadomości po wartości pola boundary
			$tmp = explode('--'.$bound['bound'], $tmp); 
			//odcięcie pierwszego elementu tablicy (informacji "This is a multi-part message in MIME format.")
			$tmp = array_slice($tmp, 1);
			foreach($tmp as $part) {
				$processed = $this->processMessagePart($part);
				//jeśli część jest załącznikiem
				if(isset($processed['meta']['ContentDisposition']) &&
				 'attachment'==$processed['meta']['ContentDisposition'])
					$processed['data'] = ''; //usuń dane załącznika (dane załącznika będą dostępne do pobrania osobno)
					
				$parts[] = $processed;
			}
		}
		$message->setMessage($parts);
		return $message;
	}
	
	/**
	 * @method processMessagePart
	 * @param $part String Część wiadomości do przetworzenia
	 * @return array[] - Przetworzona wiadomość gotowa do wysłania
	 * 
	 * Metoda przetwarza część wiadomości, wyciągając pola jej nagłówka oraz dane właściwe
	 */
	 public function processMessagePart($part) {
	 	//po nagłówkach występuje seria znaków /r/n/r/n, dzięki temu można oddzielić nagłówki od danych
	 	$tmp = explode("\r\n\r\n", $part);
		$data = implode("\r\n\r\n", array_splice($tmp, 1));
		
		//wyszukiwanie nagłówków:
		$metadata = array();
		$matches = array();
		//Content-Type:
		preg_match('/Content-Type: (?P<contenttype>[a-zA-Z0-9\/-]+)[;\r\n]{1}/',$tmp[0], $matches);
		if(isset($matches['contenttype'])) $metadata['ContentType']=$matches['contenttype'];
		
		//charset:
		preg_match('/charset=(?P<charset>[a-zA-Z0-9-]+)[;\r\n]{1}/', $tmp[0], $matches);
		if(isset($matches['charset'])) $metadata['Charset']=$matches['charset'];
		
		//name:
		preg_match('/name="(?P<name>[\w.-]+)"/', $tmp[0], $matches);
		if(isset($matches['name'])) $metadata['Name']=$matches['name'];
		
		//Content-Transfer-Encoding:
		preg_match('/Content-Transfer-Encoding: (?P<trenc>[a-zA-Z0-9-]+)[;\r\n]{1}/', $tmp[0], $matches);
		if(isset($matches['trenc'])) $metadata['ContentTransferEncoding']=$matches['trenc'];
		
		//Content-Disposition:
		preg_match('/Content-Disposition: (?P<cdisp>[a-zA-Z0-9-]+)[;\r\n]{1}/', $tmp[0], $matches);
		if(isset($matches['cdisp'])) $metadata['ContentDisposition']=$matches['cdisp'];
		
		//name:
		preg_match('/filename="(?P<filename>[\w.-]+)"/', $tmp[0], $matches);
		if(isset($matches['filename'])) $metadata['Filename']=$matches['filename'];
		
		//format:
		preg_match('/format=(?P<format>[a-zA-Z0-9-]+)[;\r\n]{1}/', $tmp[0], $matches);
		if(isset($matches['format'])) $metadata['Format']=$matches['format'];
		
		return array('data'=>$data, 'meta'=>$metadata);
	 }
	/**
	 * @method fetch 
	 * @param $what String Nazwa pola do pobrania
	 * @param $mailNo int Numer maila, z którego pobrać informacje
	 * @return String Zawartość pobranego pola
	 * 
	 * Metoda wywołująca komendę FETCH z odpowiednimi paramterami
	 * FUNKCJA MOŻE ZOSTAĆ UŻYTA TYLKO PO WYBRANIU SKRZYNKI (@see getMailboxInfo)!
	 */
	 private function fetch($what, $mailNo) {
	 	$this->send('FETCH ' . $mailNo . ' ' . $what);
		$tmp='';
		do {
			$tmp = $this->recv();
		} while('*'!=$tmp{0});
		return $this->recv();
	 }

	 
	/**
	 * @method send
	 * @param $data String Dane do wysłania
	 * @return int Ilość wysłanych danych
	 * 
	 * Metoda wysyłająca komendy do serwera
	 */
	 private function send($data) {
	 	if(NULL==$this->sck) return -1;
		return @socket_write($this->sck, $this->aId(TRUE).' '.$data ."\r\n");
	 }
	 
	 /**
	  * @method recv
	  * @return String Odebrane dane
	  * Metoda pobierająca odpowiedzi od serwera
	  */
	  private function recv() {
	  	if(NULL==$this->sck) return NULL;
		return @socket_read($this->sck, 65536);
	  }
	  
	  /**
	   * @method aId
	   * @param $inc Boolean Gdy TRUE, inkrementuje a
	   * @return String identyfikator wiadomości w formacie a000, gdzie 000 to kolejne liczby
	   * 
	   * Metoda tworząca kolejne identyfikatory wiadomości
	   */
	   private function aId($inc) {
	   	if(TRUE==$inc) $this->a++;
		$ret = sprintf("a%03d", $this->a);
		return $ret;
	   }
 }
